const uuid = require("uuid");
const axios = require("axios").default.create({
  headers: {
    "User-Agent": "okhttp/3.10.0"
  }
});
const util = require("util");
const path = require("path");
const fs = require("fs");
const buffer = require("buffer");
const url = require("url");
const crypto = require("crypto");
const qs = require("querystring")
const FormData = require("form-data");
const randomstring = require("randomstring");
const random_number = require("random-number");
const rannum = random_number;
const randomcolor = require("randomcolor");
const JsB64 = require("js-base64").Base64;
const intoStream = require("into-stream").default;
const runParallel = require("run-parallel-limit");
const LibOwhat = require("lib-owhat/lib/v0.0.1/");
const LibMeipian = require("upload-meipian/lib").WebWrapper;
const MeipianWrapperV2 = require("upload-meipian/lib/v2").MeipianWrapperV2;
const Toolbox = require("./Toolbox");
const CommonAxerrHandlerGen = require("./CommonAxerrHandler")
const TokenGetter = require("./TokenGetter").TokenGetter


const dbg = require("debug")("FileMakers");
dbg.enabled = true;
class FileMakers {

  /**
   * @private
   * @returns {String}
   * @param {String} full_path 
   * @param {Number} parts_count 
   * @param {Number} part_index 
   */
  static default_part_name_func(full_path, parts_count, part_index) {
    parts_count = parseInt(parts_count);
    if (parts_count == 1) {
      return path.basename(full_path);
    } else {
      let max_length = String(parts_count).length;
      let str_index = String(part_index + 1);
      while (str_index.length < max_length) {
        str_index = `0${str_index}`
      }
      return `${path.basename(full_path)}.sf-part${str_index}`
    }
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_7moor(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    const Gen7moorKey = () => {
      let d = new Date;
      let isos = d.toISOString();
      let isos_re = isos.replace("T", "");
      return `im/73859f20-f357-11e6-b43e-3b18b16942dc/${isos_re}/${randomstring.generate(5)}`;
    }
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_7moor();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `can't get 7moor token:${o_token.msg}`
        })
      }
      let o_mkfile = await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken,
        Gen7moorKey());
      if (!o_mkfile.ok) {
        return resolve({
          ok: false,
          msg: `error when COMMON_MAKE_FILE:${o_mkfile.msg}`
        })
      }
      // debugger
      let NOT_okresults = o_mkfile.data.filter(r => !(r.key_data && r.key_data['key']));
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map(d => {
          return {
            filepath: d.filepath,
            link: `http://fs-im-kefu.7moor.com/${d.key_data['key']}`
          }
        })
      })


    })
  }

  /**]
   * @description 妈的 这个有点特殊啊 mkblk的时候必须是使用up-z1.qiniup.com的域名 导致不能够和其他的mkblk的数据共享
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_soyoung(file_info, size_limit = 300 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    let keyGen = () => `${uuid()}-${Date.now()}.mp4`;
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_soyoung();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `can't get dú_app token:${o_token.msg}`
        })
      }
      let o_mkfile = await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken,
        keyGen(), undefined, "up-z1.qiniup.com");
      let NOT_okresults = o_mkfile.data.filter(r => !(r.key_data
        && r.key_data['name']
        && r.key_data['hash']));
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map(d => {
          return {
            filepath: d.filepath,
            link: `https://videosy.soyoung.com/${d.key_data['name']}`
          }
        })
      })

    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_hunliji(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_hunliji();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `can;t get hunliji token:${o_token.msg}`
        })
      }
      let o_mkfile = await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken, undefined, undefined, undefined, "image/gif");
      if (!o_mkfile.ok) {
        return resolve({
          ok: false,
          msg: `error when COMMON_MAKE_FILE:${o_mkfile.msg}`
        })
      }
      let NOT_okresults = o_mkfile.data.filter(r => !(r.key_data && r.key_data['image_path']))
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map(d => {
          return {
            filepath: d.filepath,
            link: `https://qnm.hunliji.com/${d.key_data['image_path']}`
          }
        })
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_owhat(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_owhat();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `can't get owhat token:${o_token.msg}`
        })
      }
      let o_mkfile = await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken,
        o_token.data.key, undefined, undefined, "image/gif");
      if (!o_mkfile.ok) {
        return resolve({
          ok: false,
          msg: `error when COMMON_MAKE_FILE:${o_mkfile.msg}`
        })
      }
      // debugger
      let NOT_okresults = o_mkfile.data.filter(r => !(r.key_data && r.key_data['key']));
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map(d => {
          return {
            filepath: d.filepath,
            link: `https://qimage.owhat.cn/${d.key_data['key']}`
          }
        })
      })
    })
  }
  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_babytree(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_babytree();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `can;t get babytree token:${o_token.msg}`
        })
      }
      let o_mkfile = await await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken, undefined, undefined, undefined, "image/gif");
      if (!o_mkfile.ok) {
        return resolve({
          ok: false,
          msg: `error when COMMON_MAKE_FILE:${o_mkfile.msg}`
        })
      }
      let NOT_okresults = o_mkfile.data.filter(e => !(e.key_data
        && e.key_data.status
        && e.key_data.data
        && e.key_data.data.thumb_info
        && e.key_data.data.thumb_info.big
        && e.key_data.data.thumb_info.big.photo_url
        && util.isString(e.key_data.data.thumb_info.big.photo_url)
        && e.key_data.data.thumb_info.big.photo_url.endsWith("_b.jpg")
      ));
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map(d => {
          let bigurl = d.key_data.data.thumb_info.big.photo_url
          let indexOf_b = bigurl.indexOf("_b");
          return {
            filepath: d.filepath,
            link: bigurl.substr(0, indexOf_b)
          }
        })
      })

    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_daovoice(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_daovoice(path.extname(file_info.path));
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `cant get DaoVoice token:${o_token.msg}`
        })
      }
      let o_mkfile = await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken,
        o_token.data.key);
      if (!o_mkfile.ok) {
        return resolve({
          ok: false,
          msg: `error when COMMON_MAKE_FILE:${o_mkfile.msg}`
        })
      }
      // debugger
      let NOT_okresults = o_mkfile.data.filter(r => !(r.key_data && r.key_data['key']));
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map(d => {
          return {
            filepath: d.filepath,
            link: `https://daocom-uploads-cdn.daovoice.io/${d.key_data['key']}`
          }
        })
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_hiaita(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    let key_gen = () => {
      let d = new Date;
      return `pic/${d.getFullYear()}${d.getMonth()}${d.getDate()}/${
        uuid()
        }.gif`
    }
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_hiaita();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `cant get hiaita token:${o_token.msg}`
        })
      }
      let thekey = key_gen();
      let o_mkfile = await await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken,
        thekey);
      if (!o_mkfile.ok) {
        return resolve({
          ok: false,
          msg: `error when COMMON_MAKE_FILE:${o_mkfile.msg}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map((d, i, a) => {
          let base = part_name_func(thekey, a.length, i);
          return {
            filepath: d.filepath,
            link: `http://pic.hiaita.com/${url.parse(
              path.join(path.dirname(thekey), base)
            ).path}`
          }
        })
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_dú(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    let rn = random_number;
    let keyGen = () => `miaohong/post/photo/prod/6566${parseInt(rn({
      min: 200,
      max: 415
    }))}/${(Date.now() * 10000 + parseInt(rn({ min: 1000, max: 5000 }))) / 1E16}E12`
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_dú();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `can't get dú_app token:${o_token.msg}`
        })
      }
      let o_mkfile = await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken,
        keyGen(), undefined, undefined, "image/gif");
      // debugger
      let NOT_okresults = o_mkfile.data.filter(r => !(r.key_data && r.key_data['key']));
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map(d => {
          return {
            filepath: d.filepath,
            link: `https://du.hupucdn.com/${d.key_data['key']}`
          }
        })
      })
    })
  }


  /**
 * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
 * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
 * @param {Number} size_limit 
 * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
 */
  static mkfile_meipian(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    let meipian_keygen = () => `user/581225${
      parseInt(random_number({ min: 10, max: 40 }))
      }/${randomstring.generate(32)}.gif`;
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_meipian();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `can't get meipian token:${o_token.msg}`
        })
      }
      let mfw_part_name_func = (a, b, c) => {
        return `${part_name_func(a, b, c)}`;
      };
      let o_ins = await LibMeipian.getGoodInstance();
      if (!o_ins.ok) {
        return resolve({
          ok: false,
          msg: `can  not get valid meipian instance:${o_ins.msg}`
        })
      };
      let o_pad = await o_ins.instance.create4MBPaddingCtx();
      /**
       * @returns {Promise<{ok:Boolean,msg:String,data:{ctx:string,chunk_size:Number}}>}
       */
      let padding_func = () => new Promise(async resolve => {
        resolve(o_pad);
      });



      /**
       * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
       * @param {Number} time 
       */
      let timeout_try = (time = 30 * 1000) => new Promise(try_resolve => {
        let the_key = meipian_keygen();
        console.log("[mkfile][meipian]start...", path.basename(file_info.path));
        let flag = false;
        FileMakers.COMMON_MAKE_FILE(file_info.path,
          file_info.data, size_limit, mfw_part_name_func,
          o_token.data.uptoken, the_key, padding_func, undefined, "image/gif")
          .then(o_mkfile => {
            flag = true;
            if (!o_mkfile.ok) {
              return try_resolve({
                ok: false,
                msg: `error when COMMON_MAKE_FILE:${o_mkfile.msg}`
              })
            }
            console.log("[mkfile][meipian]end...", path.basename(file_info.path), o_mkfile.ok);
            try_resolve({
              ok: true,
              msg: "ok",
              data: o_mkfile.data.map((d, i, a) => {
                let base = mfw_part_name_func(the_key, a.length, i);
                return {
                  filepath: d.filepath,
                  link: `http://static2.ivwen.com/${url.parse(path.join(path.dirname(the_key), base)).path}`
                }
              })
            });
          });
        setTimeout($ => {
          console.log('[mkfile][meipian][timeout]', time, path.basename(file_info.path))
          if (!flag) {
            try_resolve({
              ok: false,
              msg: `MAN TIME OUT(${time}) for making file`
            });
          }
        }, time)
      });

      for (let re = 1; re <= 5; re++) {
        let o_try = await timeout_try(10 * 1000 * re);
        if (o_try.ok) {
          return resolve({
            ok: true,
            msg: `OK when ${re} time try`,
            data: o_try.data
          })
        }
        if (re == 5) {
          return resolve({
            ok: false,
            msg: `failed after 5 try:${o_try.msg}`
          })
        } else {
          console.log("timeout_try failed:", o_try.msg)
          await TokenGetter.pause_promise_gen(500);
        }
      }
    });
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_tuniu(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    /**
     * @description tuniu的content-type好像会受到key的影响
     * 比如下面两个相同文件
     * https://s.tuniu.net/qn/image/34e11045-7415-4ea9-a793-9f6f9f308771.tar#application/x-tar
     * https://s.tuniu.net/qn/image/0d3019c5-d47d-4ce0-a05d-efbb76629490.sample#application/x-tar
     */
    let tuniuKeyGen = () => `qn/image/${uuid()}.gif`;
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_tuniu();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `cant get tuniu token:${o_token.msg}`
        })
      }
      let o_meipianIns = await MeipianWrapperV2.getGoodInstance(58122550, "meipian65huji")
      if (!o_meipianIns.ok) {
        return resolve({
          ok: false,
          msg: `can noy get meipian instance for padding:${o_meipianIns.msg}`
        })
      }
      let o_pad = await o_meipianIns.data.meipian.create4MBImagePaddingCtx();
      /**
       * @returns {Promise<{ok:Boolean,msg:String,data:{ctx:string,chunk_size:Number}}>}
       */
      let padding_func = () => new Promise(async resolve => {
        resolve(o_pad);
      });
      let o_mkfile = await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken,
        o_token.data.key, padding_func, undefined, "image/gif");
      if (!o_mkfile.ok) {
        return resolve({
          ok: false,
          msg: `error when COMMON_MAKE_FILE:${o_mkfile.msg}`
        })
      }
      // debugger
      let NOT_okresults = o_mkfile.data.filter(r => !(r.key_data && r.key_data['key']));
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map(d => {
          return {
            filepath: d.filepath,
            link: `https://s.tuniu.net/${d.key_data['key']}`
          }
        })
      })

    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_udesk(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    let udesk_key_gen = () => {
      let ts = Date.now();
      return `${randomstring.generate(parseInt(random_number({ min: 5, max: 10 })))}_${parseInt(ts / 1000)}_${ts % 1000}${path.extname(file_info.path)}`
    };
    return new Promise(async resolve => {
      let o_token = await TokenGetter.get_udesk();
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `cant get udesk token:${o_token.msg}`
        })
      }
      let o_mkfile = await FileMakers.COMMON_MAKE_FILE(file_info.path,
        file_info.data, size_limit, part_name_func,
        o_token.data.uptoken,
        udesk_key_gen());
      if (!o_mkfile.ok) {
        return resolve({
          ok: false,
          msg: `error when COMMON_MAKE_FILE:${o_mkfile.msg}`
        })
      }
      // debugger
      let NOT_okresults = o_mkfile.data.filter(r => !(r.key_data && r.key_data['key']));
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: o_mkfile.data.map(d => {
          return {
            filepath: d.filepath,
            link: `https://qn-im.udesk.cn/${d.key_data['key']}`
          }
        })
      })


    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,link:String}>>}}
   * @param {{path:String,data:Array<{ctx:String,chunk_size:Number}>}} file_info 
   * @param {Number} size_limit 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   */
  static mkfile_pianke(file_info, size_limit = 20 * 1024 * 1024 * 1024, part_name_func = FileMakers.default_part_name_func) {
    const S4MB = 4 * 1024 * 1024;
    const size_limit_bytes = parseInt(size_limit);
    if (size_limit_bytes < 4 * 1024 * 1024) {
      size_limit_bytes = 4 * 1024 * 1024;
    }
    let ctx_infos = file_info.data;
    let file_relapath = file_info.path;
    return new Promise(async resolve => {
      /**
       * @todo 不能使用COMMON_MAKE_FILE,pianke的token必须每个token都对应一个规定好的key
       */
      /**@type {Array<Array<{ctx:String,chunk_size:number}>>} */
      let ctx_and_sizees = [];
      let max_chunk_count = Math.ceil(size_limit_bytes / S4MB);
      let i = 0;
      while (true) {
        let end_index = i + max_chunk_count - 1;
        if (end_index > ctx_infos.length - 1) {
          end_index = ctx_infos.length - 1;
        }
        ctx_and_sizees.push(ctx_infos.slice(i, end_index + 1));
        i = end_index + 1;
        if (i > ctx_infos.length - 1) {
          break;
        }
      }
      /**@type {Array<{filepath:String,key_data:any}>} */
      let results = [];
      for (let part_index = 0; part_index < ctx_and_sizees.length; part_index++) {
        try {
          let o_try = await new Promise(async (simple_resolve, simple_reject) => {
            let payload = ctx_and_sizees[part_index].map(e => e.ctx).join(",");
            let part_size = ctx_and_sizees[part_index].map(e => e.chunk_size).reduce((a, b) => a + b);
            let axios_post_url = `http://upload.qiniup.com/mkfile/${part_size}/`;
            let o_token = await TokenGetter.get_pianke(path.extname(file_relapath).replace(".", ""));
            if (!o_token.ok) {
              return simple_reject(`failed to get piank token for part ${part_index + 1}:${o_token.msg} `)
            }
            axios_post_url = `http://upload.qiniup.com/mkfile/${part_size}/key/${JsB64.encode(o_token.data.key, false)}`;
            axios.post(axios_post_url, payload, {
              headers: {
                "Content-Length": payload.length,
                "Authorization": `UpToken ${o_token.data.uptoken}`,
                "Content-Type": "text/plain;charset=UTF-8"
              },
              validateStatus: s => s == 200 || s == 413,
              timeout: 60 * 1000
            }).then(axresp => {
              if (axresp.data && axresp.status == 200) {
                results.push({
                  filepath: path.join(path.dirname(file_relapath), part_name_func(file_relapath, ctx_and_sizees.length, part_index)),
                  key_data: axresp.data
                });
                simple_resolve("ok")
              } else {
                throw new Error(`no sanity axresp:${util.inspect(axresp.data)}`)
              }
            }).catch(axerr => {
              simple_reject(axerr.message ? axerr.message : util.inspect(axerr))
            })

          });
          // debugger
        } catch (err) {
          return resolve({
            ok: false,
            msg: `error when mkfile part ${part_index + 1}:${err}`
          })
        }
      }
      let NOT_okresults = results.filter(r => !(r.key_data && r.key_data['key']));
      if (NOT_okresults.length > 0) {
        return resolve({
          ok: false,
          msg: `存在不合预期的mkfile结果:${util.inspect(NOT_okresults)}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: results.map(d => {
          return {
            filepath: d.filepath,
            link: `https://qnimg.pianke.me/${d.key_data['key']}`
          }
        })
      })



    })
  }



  /**
   * @private
   * @returns {Promise<{ok:Boolean,msg:String,data:Array<{filepath:String,key_data:any}> }>}
   * @param {String} file_relapath
   * @param {Array<{ctx:String,chunk_size:Number}>} ctx_infos 
   * @param {Number} size_limit_bytes 
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} part_name_func 
   * @param {String} uptoken
   * @param {String} key 可选
   * @param {String} qiniu_host
   * @param {String} defined_mimeType
   * @param {()=>Promise<{ok:Boolean,msg:String,data:{ctx:string,chunk_size:Number}}>} padding_gen 可选,生成4MB的填充块填充到最前面,变成mfwfile
   */
  static COMMON_MAKE_FILE(file_relapath, ctx_infos, size_limit_bytes, part_name_func, uptoken, key = "", padding_gen = null, qiniu_host = "upload.qiniup.com", defined_mimeType = "") {
    const S4MB = 4 * 1024 * 1024;
    size_limit_bytes = parseInt(size_limit_bytes);
    if (size_limit_bytes < 4 * 1024 * 1024) {
      size_limit_bytes = 4 * 1024 * 1024
    }
    return new Promise(async resolve => {
      /**@type {Array<Array<{ctx:String,chunk_size:number}>>} */
      let ctx_and_sizees = [];
      let max_chunk_count = Math.ceil(size_limit_bytes / S4MB);
      let i = 0;
      while (true) {
        let end_index = i + max_chunk_count - 1;
        if (end_index > ctx_infos.length - 1) {
          end_index = ctx_infos.length - 1;
        }
        ctx_and_sizees.push(ctx_infos.slice(i, end_index + 1));
        i = end_index + 1;
        if (i > ctx_infos.length - 1) {
          break;
        }
      }
      /**@type {Promise<{ok:Boolean,msg:String,data:{ctx:string,chunk_size:Number}}} */
      let p_padding;
      if (padding_gen) {
        p_padding = padding_gen();
      }

      /**@type {Array<{filepath:String,key_data:any}>} */
      let results = [];
      for (let part_index = 0; part_index < ctx_and_sizees.length; part_index++) {
        try {
          let o_try = await new Promise(async (simple_resolve, simple_reject) => {
            if (padding_gen) {
              let o_padding = await p_padding;
              if (!o_padding.ok) {
                return simple_reject(`failed to create padding chunk:${o_padding.msg}`)
              }
              ctx_and_sizees[part_index].unshift({
                ctx: o_padding.data.ctx,
                chunk_size: o_padding.data.chunk_size
              })
            }
            let payload = ctx_and_sizees[part_index].map(e => e.ctx).join(",");
            let part_size = ctx_and_sizees[part_index].map(e => e.chunk_size).reduce((a, b) => a + b);
            let axios_post_url = `http://${qiniu_host}/mkfile/${part_size}/`;
            if (key) {
              let _finish_key = url.parse(path.join(path.dirname(key), part_name_func(key, ctx_and_sizees.length, part_index))).path;
              // console.log("_finish key is ", _finish_key);
              axios_post_url = `http://${qiniu_host}/mkfile/${part_size}/key/${JsB64.encode(_finish_key, false)}/`;
            };
            if (defined_mimeType) {
              axios_post_url = `${axios_post_url}mimeType/${JsB64.encode(defined_mimeType, false)}`
            }
            (async function retrying() {
              let MBs = parseInt(part_size / 1024 / 1024);
              let wait_sec = ($ => {
                let s = MBs / 100;
                if (s > 60) {
                  s = 60;
                }
                return parseInt(s);
              })()
              for (let re = 1; re <= 5; re++) {
                let o_http = await Toolbox.WrapHttpPostStream(
                  intoStream(payload),
                  axios_post_url,
                  {
                    "Content-Length": payload.length,
                    "Authorization": `UpToken ${uptoken}`,
                    "Content-Type": "text/plain;charset=UTF-8"
                  },
                  parseInt(wait_sec * 1000 * 1.3)
                );
                if (o_http.ok && o_http.json_data) {
                  results.push({
                    filepath: path.join(path.dirname(file_relapath),
                      part_name_func(file_relapath, ctx_and_sizees.length, part_index)),
                    key_data: o_http.json_data
                  })
                  return simple_resolve("ok")
                } else {
                  if (re == 5) {
                    return simple_reject(o_http.ok ? util.inspect(o_http) : o_http.msg)
                  } else {
                    console.log("retrying mkfile...key=", key, file_relapath);
                    await TokenGetter.pause_promise_gen(10);
                  }
                }
              }
            })();
          });
          // debugger
        } catch (err) {
          return resolve({
            ok: false,
            msg: `error when mkfile part ${part_index + 1}:${err}`
          })
        }
      }
      resolve({
        ok: true,
        msg: "ok",
        data: results
      })

    })
  }
}

module.exports = {
  FileMakers: FileMakers
}